來到 Day 10 啦!
完成今天的內容後,這個系列也正式走完三分之一了。
前面幾天,我們一直使用 Nginx 當作練習用的 Container。原因很簡單:剛開始學習 Kubernetes 時,最重要的是先理解 Pod、Deployment、Service、Label 與 Selector 之間的關係。如果一開始就加入自己撰寫的 Application,當服務無法啟動時,我們可能很難立刻判斷問題到底出在程式碼、Docker Image,還是 Kubernetes 設定。
因此前面的策略是:
先專心學 Kubernetes
不要同時 Debug Application
不過從今天開始,我們要跨出重要的一步:不再部署別人準備好的 Nginx Image,而是自己撰寫一個 FastAPI 服務、建立 Docker Image,再將它部署到 Kubernetes。
今天完成的第一版架構如下:
Client
↓
Kubernetes Service
↓
FastAPI Pod
目前 Application 還很簡單,但之後我們會逐步加入 Redis、PostgreSQL、健康檢查、設定管理與其他功能,慢慢把它擴充成真正的微服務系統。
今天會完整走過以下流程:
撰寫 Python 程式
↓
建立 Docker Image
↓
將 Image 載入 kind
↓
建立 Kubernetes Deployment
↓
Deployment 建立 ReplicaSet
↓
ReplicaSet 建立 Pod
↓
Service 將流量導向 Pod
↓
使用 port-forward 從 Mac 存取服務
這條流程非常重要。未來不論你部署的是 Python、Java、Go 或 Node.js 服務,基本概念都不會有太大差異。
假設目前的專案結構如下:
cka-project/
├── app/
└── k8s/
其中:
app/
用來放 FastAPI 程式、Python 套件清單與 Dockerfile。
k8s/
則用來存放 Kubernetes YAML。
今天完成後,專案結構會變成:
cka-project/
├── app/
│ ├── main.py
│ ├── requirements.txt
│ └── Dockerfile
└── k8s/
└── 01-api.yaml
如果目錄還不存在,可以先建立:
mkdir -p app k8s
接下來的指令,除非特別說明,都會假設你位於專案根目錄,也就是 cka-project/。
可以使用以下指令確認目前位置:
pwd
首先建立:
app/main.py
內容如下:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {
"message": "Hello from Kubernetes"
}
@app.get("/health/live")
def live():
return {
"status": "alive"
}
這是一個非常精簡的 FastAPI Application,但它已經是一個可以接收 HTTP Request 並回傳 JSON 的 Web API。
第一行:
from fastapi import FastAPI
代表我們從 fastapi 套件中匯入 FastAPI 類別。
接著建立 Application 物件:
app = FastAPI()
這個 app 就是整個 Web Application 的核心。稍後啟動 Uvicorn 時,我們會告訴 Uvicorn:
請到 main.py 裡找到 app 這個物件
因此啟動指令才會寫成:
uvicorn main:app
其中:
main
代表 main.py。
app
代表 main.py 裡的 app = FastAPI()。
合起來就是:
main.py 裡面的 app 物件
這一段定義首頁 API:
@app.get("/")
def root():
return {
"message": "Hello from Kubernetes"
}
@app.get("/") 表示:
當 Application 收到對 / 發出的 HTTP GET Request 時
執行下面的 root() 函式
例如使用者送出:
GET /
FastAPI 就會執行:
def root():
然後將 Python Dictionary:
{
"message": "Hello from Kubernetes"
}
自動轉換成 JSON Response:
{
"message": "Hello from Kubernetes"
}
所以我們不需要自己處理 JSON 序列化,也不需要手動設定 Content-Type: application/json,FastAPI 會替我們完成。
第二個 Endpoint 是:
@app.get("/health/live")
def live():
return {
"status": "alive"
}
當我們訪問:
/health/live
Application 會回傳:
{
"status": "alive"
}
目前這個 API 看起來很普通,但之後它會提供給 Kubernetes 的 Liveness Probe 使用。
Liveness Probe 的目的,是讓 Kubernetes 定期確認 Container 裡的 Application 是否仍然正常運作。
未來 Kubernetes 可能會定期發送:
GET /health/live
如果 Application 持續無法回應,Kubernetes 就可以判斷 Container 可能已經失去正常服務能力,並重新啟動它。
今天先準備好 Endpoint,之後再正式加入 Probe 設定。
接著建立:
app/requirements.txt
內容如下:
fastapi
uvicorn[standard]
這個檔案列出 Application 執行時需要安裝的 Python 套件。
fastapi 是 Web Framework,負責:
不過 FastAPI 本身不是實際監聽 Port 的 HTTP Server,因此我們還需要 Uvicorn。
uvicorn[standard]
Uvicorn 是一個 ASGI Server,負責真正啟動 Server、監聽 Port,並將收到的 HTTP Request 交給 FastAPI 處理。
兩者可以簡單理解成:
Uvicorn
負責接收網路請求
FastAPI
負責決定如何處理請求
在正式專案中,通常會固定套件版本,避免套件更新後產生非預期差異,例如:
fastapi==指定版本
uvicorn[standard]==指定版本
不過目前是學習環境,先使用簡單版本即可。
在 app/ 目錄中建立:
app/Dockerfile
內容如下:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
-r requirements.txt
COPY main.py .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Dockerfile 描述了 Docker 應該如何將我們的程式碼製作成 Container Image。
接下來逐一理解每一個指令。
FROM:選擇 Base ImageFROM python:3.12-slim
建立 Docker Image 時,我們通常不會從完全空白的環境開始,而是使用既有的 Base Image。
這裡使用的是 Python 官方提供的:
python:3.12-slim
其中:
python
代表官方 Python Image。
3.12
代表 Python 版本。
slim
表示這是一個相對精簡的 Debian-based Image。它保留執行 Python 所需的基本環境,但移除了許多目前用不到的系統套件,因此 Image 通常比完整版本更小。
使用 Base Image 後,我們不需要自己安裝作業系統與 Python,可以直接開始安裝 Application 需要的套件。
WORKDIR:指定工作目錄WORKDIR /app
這行會在 Image 裡設定主要工作目錄:
/app
後續的 COPY、RUN 與 CMD 等指令,都會以這個目錄作為主要執行位置。
如果 /app 不存在,Docker 會自動建立它。
可以把它理解成在 Container 裡執行:
cd /app
因此後面這一行:
COPY requirements.txt .
最後會將檔案放到:
/app/requirements.txt
這裡的 . 指的是目前的 WORKDIR,也就是 /app。
COPY:複製套件清單COPY requirements.txt .
這行會將 Build Context 中的:
requirements.txt
複製到 Image 裡的:
/app/requirements.txt
要注意,COPY 左邊是本機 Build Context 裡的檔案,右邊則是 Image 裡的路徑。
稍後我們會執行:
docker build -t cka-api:v1 ./app
最後的 ./app 就是 Build Context。
因此 Docker 能看見的是:
app/
├── main.py
├── requirements.txt
└── Dockerfile
而不是整個專案裡的所有檔案。
RUN:在 Build 階段安裝套件RUN pip install \
--no-cache-dir \
-r requirements.txt
RUN 表示這個指令會在建立 Image 的過程中執行。
它實際執行的內容相當於:
pip install --no-cache-dir -r requirements.txt
其中:
-r requirements.txt
表示依照 requirements.txt 的內容安裝套件。
--no-cache-dir
表示不要保留 pip 下載套件時產生的快取,以減少 Image 中不必要的檔案與容量。
這一步完成後,FastAPI 與 Uvicorn 就會被安裝進 Image。
Dockerfile 沒有一開始就把所有程式碼全部複製進去,而是先執行:
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
-r requirements.txt
最後才複製:
COPY main.py .
這與 Docker Layer Cache 有關。
Dockerfile 中的每個步驟都可能形成一層快取。如果我們只修改 main.py,但沒有修改 requirements.txt,Docker 就有機會重複使用先前安裝 Python 套件的 Layer,不必每次重新下載與安裝所有套件。簡單說就是 requirements.txt 改動機率較低,所以讓他位在改動可能更平凡的 main.py 上面,想讓 Build Image 加快。
概念如下:
requirements.txt 沒有改
↓
重複使用 pip install 的快取
↓
只重新複製新的 main.py
↓
Build 速度更快
這是撰寫 Dockerfile 時很常見的優化方式。
COPY:複製 ApplicationCOPY main.py .
這行會把本機的:
app/main.py
複製到 Image 裡的:
/app/main.py
此時 Image 裡的重要內容大致如下:
/app/
├── main.py
└── requirements.txt
同時 Python 環境中也已經安裝 FastAPI 與 Uvicorn。
EXPOSE:說明 Application 使用的 PortEXPOSE 8000
這表示這個 Container 預期會在 Port 8000 提供服務。
不過要特別注意:
EXPOSE 不會自動把 Container Port 開放到 Mac
它比較像是 Image 提供的說明資訊,告訴使用者與相關工具:
這個 Application 預計使用 8000 Port
如果使用 Docker 在本機執行,仍然需要使用 -p 映射 Port:
docker run -p 8000:8000 cka-api:v1
如果部署到 Kubernetes,也還是要在 Deployment 與 Service 中設定相應的 Port。
CMD:Container 啟動時執行的指令CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
這是 Container 啟動時預設執行的指令,等同於:
uvicorn main:app \
--host 0.0.0.0 \
--port 8000
其中:
main:app
表示載入 main.py 裡的 app 物件。
--host 0.0.0.0
表示接受從 Container 外部進來的連線。
如果只監聽:
127.0.0.1
服務可能只能從 Container 自己內部存取,其他 Container 或 Kubernetes Service 無法連入。因此在 Container 環境中,通常要監聽:
0.0.0.0
最後:
--port 8000
表示 Uvicorn 會監聽 Container 的 Port 8000。
RUN 與 CMD 的差別這兩個指令非常容易混淆,一定要分清楚。
RUN (Image 建立時執行)RUN pip install ...
是在建立 Image 時執行。
CMD (Container 啟動時執行)CMD ["uvicorn", ...]
是在 Container 啟動時執行。
可以記成:
RUN
= Build Image 時執行
CMD
= Run Container 時執行
例如:
docker build
↓
執行 RUN
↓
產生 Image
之後:
docker run
↓
建立 Container
↓
執行 CMD
當 Kubernetes 建立 Pod 並啟動 Container 時,也會執行 Image 裡設定的 CMD。
確認目前位於 app 目錄(含有 Dockerfile 的位置),然後執行:
docker build -t cka-api:v1 .

完整意思是:
使用目前目錄 . 作為 Build Context
讀取目前目錄下的 ./Dockerfile
建立一個名為 cka-api:v1 的 Image
其中:
-t
是 --tag 的縮寫,用來替 Image 設定名稱與 Tag。
這次的 Image 名稱是:
cka-api
Tag 是:
v1
完整 Image Reference 為:
cka-api:v1
Tag 可以用來區分不同版本,例如:
cka-api:v1
cka-api:v2
cka-api:v3
建立完成後,可以查看本機 Image:
docker images

在放進 Kubernetes 之前,建議先確認 Docker Image 本身能不能正常運作。
執行:
docker run --rm -p 8000:8000 cka-api:v1

打開 http://0.0.0.0:8000/ 便可看到我們先前撰寫的 {"message":"Hello from k8s"} 出現在網頁上

其中:
--rm
表示 Container 停止後自動刪除,避免留下不需要的 Container。
-p 8000:8000
表示:
Mac Port 8000
↓
Container Port 8000
開啟另一個 Terminal,測試首頁:
curl http://localhost:8000

應該得到:
{"message":"Hello from Kubernetes"}
再測試健康檢查:
curl http://localhost:8000/health/live

應該得到:
{"status":"alive"}
如果這個階段就無法成功,代表問題通常出在:
此時先不要急著部署 Kubernetes,否則會同時增加排查範圍。
測試完成後,可以在執行 Container 的 Terminal 按下:
Control + C
停止 Container。
我們剛剛建立的:
cka-api:v1
目前存在於 Mac 上的 Docker 環境。
但是 kind 建立 Kubernetes Cluster 的方式比較特別:kind 的名稱來自 Kubernetes IN Docker,它會使用 Docker Container 模擬 Kubernetes Node。
也就是說,目前其實有兩個不同的範圍:
Mac 的 Docker Image
以及:
kind Node 使用的 Container Runtime Image
即使 Mac 上執行:
docker images
看得到 cka-api:v1,也不代表 kind Node 裡一定能直接使用它。
如果 Kubernetes 找不到本機 Image,它可能會嘗試前往外部 Registry 下載:
cka-api:v1
但這個 Image 並沒有上傳到 Docker Hub 或其他 Registry,因此 Pod 可能會出現:
ErrImagePull
或:
ImagePullBackOff
所以我們需要將 Image 載入 kind Cluster。
執行:
kind load docker-image \
cka-api:v1 \
--name cka-lab

這個指令表示:
將 Mac 上的 cka-api:v1
載入名為 cka-lab 的 kind Cluster
其中:
--name cka-lab
必須與建立 kind Cluster 時使用的 Cluster Name 相同。
可以先查看目前有哪些 kind Cluster:
kind get clusters

如果結果包含:
cka-lab
就代表 Cluster 名稱正確。
每次重新 Build 新版本的本機 Image 後,都要記得重新執行 kind load docker-image,否則 kind Node 可能仍然使用舊 Image。
接著建立:
k8s/01-api.yaml
先直接在同一份yaml 寫了兩個k8s resource:Deployment、Service
中間使用:--- 分隔不同 Resource。
內容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: cka-lab
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: cka-api:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: cka-lab
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8000
第一個 Resource 是 Deployment:
apiVersion: apps/v1
kind: Deployment
Deployment 的主要工作不是直接執行 Application,而是管理 Pod 的期望狀態。
我們設定:
spec:
replicas: 2
表示希望 Kubernetes 維持兩個 FastAPI Pod。
背後的關係是:
Deployment
↓
ReplicaSet
↓
2 個 Pod
如果其中一個 Pod 因故消失,ReplicaSet 會發現實際 Pod 數量只剩一個,接著自動建立新的 Pod,讓實際狀態回到我們要求的兩個。
Deployment 中有兩個重要設定:
selector:
matchLabels:
app: api
以及:
template:
metadata:
labels:
app: api
template.metadata.labels 會放到 Deployment 建立的每個 Pod 上。
因此 Pod 會擁有:
app=api
這個 Label。
Deployment 的 Selector 則表示:
我要管理 Label 為 app=api 的 Pod
所以這兩個地方必須一致:
Deployment Selector
↓
app=api
↑
Pod Template Label
如果不一致,Deployment 就無法正確管理自己建立的 Pod,Kubernetes 通常也會拒絕這份設定。
Container 設定如下:
containers:
- name: api
image: cka-api:v1
這代表 Pod 中會啟動一個名為:
api
的 Container,並使用:
cka-api:v1
這個 Image。
當 Container 啟動時,就會執行 Dockerfile 裡的:
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
因此 FastAPI 最後會在 Container 的 Port 8000 提供服務。
(複習!!
RUN 是 Image 建立時執行
CMD 則是容器啟動時運行
)
imagePullPolicy: IfNotPresent?imagePullPolicy: IfNotPresent
這表示:
如果 Node 本機已經有這個 Image,就直接使用
只有找不到時才嘗試下載
我們已經透過:
kind load docker-image cka-api:v1 --name cka-lab
將 Image 放進 kind Node,因此 Kubernetes 應該直接使用 Node 裡的 Image。
如果使用本機 Image,卻設定成:
imagePullPolicy: Always
Kubernetes 每次建立 Container 時都會嘗試從外部 Registry 下載 Image。由於 cka-api:v1 沒有上傳到 Registry,Pod 就可能啟動失敗。
在正式環境中,Image 通常會先推送到 Container Registry,例如:
Docker Hub
GitHub Container Registry
Amazon ECR
Google Artifact Registry
Kubernetes Node 再從 Registry 拉取 Image。但目前使用 kind 本機練習,因此直接使用 kind load docker-image 即可。
containerPort 的作用ports:
- containerPort: 8000
這表示 Application 預期在 Container 的 Port 8000 提供服務。
它與 Dockerfile 裡的:
EXPOSE 8000
概念相似,主要是描述 Container 使用的 Port,讓設定與閱讀者更容易理解。
但要注意:
containerPort 本身不會建立 Service
也不會將 Port 開放到 Mac
真正負責讓其他 Pod 或 Service 將流量送進來的,仍然是後面的 Kubernetes Service。
第二個 Resource 是 Service:
apiVersion: v1
kind: Service
Service 的名稱是:
metadata:
name: api
它的 Selector 是:
selector:
app: api
這表示 Service 會尋找具有以下 Label 的 Pod:
app=api
Deployment 建立的 Pod 剛好也有:
labels:
app: api
因此 Service 可以找到這些 FastAPI Pod。
整個對應關係如下:
Service Selector
app=api
↓
Pod Label
app=api
Service 不會透過 Deployment 名稱尋找 Pod,也不是因為兩者都叫 api 才互相連接。真正建立關聯的是:
Selector 與 Label
即使 Deployment 改名,只要 Pod Label 仍然符合 Service Selector,Service 依然可以將流量送到 Pod。
Service 的 Port 設定如下:
ports:
- port: 80 (自己對外提供的 Port 號,也就是別人會從 80 port 進來)
targetPort: 8000 (Service 收到流量後,要把它轉送到 Pod 的 8000 Port)
其中:
port: 80
代表 Service 自己對外提供的 Port。
targetPort: 8000
代表 Service 收到流量後,要將它轉送到 Pod 的 Port 8000。
因此請求流向是:
Service Port 80 (別人從80進來)
↓
Pod Port 8000 (Service送去給8000)
↓
Uvicorn
↓
FastAPI
這兩個 Port 不一定要相同。
Service 可以使用大家熟悉的 HTTP Port 80,Application 則繼續在 Container 裡使用 Port 8000。
因為沒有指定:
type:
所以 Service 預設類型是:
ClusterIP
ClusterIP Service 主要提供 Cluster 內部存取,Mac 瀏覽器不能直接連線,因此稍後會使用 kubectl port-forward。
如果前面曾經建立同名的 Nginx Deployment 與 Service,可以先刪除:
kubectl delete deployment api \
-n cka-lab \
--ignore-not-found
kubectl delete service api \
-n cka-lab \
--ignore-not-found
--ignore-not-found 表示如果 Resource 原本不存在,不要把它當成錯誤。
接著套用新的設定:
kubectl apply -f k8s/01-api.yaml
應該會看到類似結果:
deployment.apps/api created
service/api created

如果 Namespace 還不存在,需要先建立:
kubectl create namespace cka-lab
然後再重新執行 kubectl apply。
先查看 Deployment:
kubectl get deployments \
-n cka-lab
會看到:
其中:
READY 2/2
表示我們要求兩個 Replica,目前兩個都已經 Ready。
也可以等待 Deployment 完成更新:
kubectl rollout status deployment/api \
-n cka-lab
成功時會看到類似:
deployment "api" successfully rolled out
查看 Deployment 建立的 ReplicaSet:
kubectl get replicasets \
-n cka-lab

再查看 Pod:
kubectl get pods \
-n cka-lab \
-o wide
得到:

因為 Deployment 設定:
replicas: 2
所以應該會看到兩個 Pod。
READY 顯示:
1/1
代表 Pod 裡有一個 Container,而且這個 Container 已經 Ready。
STATUS 顯示:
Running
代表 Container 已經啟動。
可以透過 Deployment 查看 FastAPI Log:
kubectl logs deployment/api \
-n cka-lab
會看到 Uvicorn 的啟動資訊:

因為 Deployment 有兩個 Pod,這個指令通常會選擇其中一個 Pod 顯示 Log。
如果要分別查看 Pod,可以先取得名稱:
kubectl get pods -n cka-lab
再指定其中一個:
kubectl logs <Pod名稱> \
-n cka-lab
查看 Service:
kubectl get service \
-n cka-lab
得到:
!
Service 類型是 ClusterIP,而且使用 Port 80。
接著可以查看 Service 找到的 Endpoint:
kubectl get endpoints api \
-n cka-lab
正常情況下,應該可以看到兩個 Pod IP,以及它們使用的 Port 8000:
https://ithelp.ithome.com.tw/upload/images/20260908/20168537eJq3EmL4Hf.png
這代表 Service 已經透過 Selector 找到兩個 FastAPI Pod。
如果 ENDPOINTS 顯示:
<none>
通常要檢查 Service Selector 與 Pod Label 是否一致:
kubectl get pods \
-n cka-lab \
--show-labels
Service 尋找的是:
app=api
因此 Pod 也必須擁有相同 Label。
目前 api 是 ClusterIP Service,只能直接在 Kubernetes Cluster 內部使用。
為了從 Mac 存取它,我們可以執行:
kubectl port-forward \
service/api \
8080:80 \
-n cka-lab
這裡的:
8080:80
意思是:
Mac localhost:8080
↓
Kubernetes Service api:80
接著 Service 再根據自己的設定,把流量送到:
Pod:8000
完整流程如下:
Mac localhost:8080
↓
kubectl port-forward
↓
Service api:80
↓
Service Selector:app=api
↓
其中一個 FastAPI Pod:8000
↓
Uvicorn
↓
FastAPI
執行 port-forward 後,這個 Terminal 需要保持開啟。
正常情況下會看到:
Forwarding from 127.0.0.1:8080 -> 8000
!
雖然指令指定的是 Service Port 80,但 kubectl 最後會根據 Service 的 targetPort,將請求導向 Pod 的 Port 8000。
開啟另一個 Terminal:
curl http://localhost:8080
得到:
https://ithelp.ithome.com.tw/upload/images/20260908/20168537Aoy4JuBQu8.png
也可以直接使用瀏覽器開啟:
http://localhost:8080
FastAPI 會回傳:
{
"message": "Hello from Kubernetes"
}
接著測試:
curl http://localhost:8080/health/live
得到:
{"status":"alive"}
這表示請求已經成功經過:
Mac
↓
port-forward
↓
Kubernetes Service
↓
FastAPI Pod
↓
Uvicorn
↓
FastAPI Endpoint
FastAPI 會自動產生互動式 API 文件。
保持 port-forward 執行,然後在瀏覽器開啟:
http://localhost:8080/docs

你會看到 Swagger UI,其中包含目前建立的兩個 API:
GET /
GET /health/live
也可以直接在頁面中送出 Request。
另一份 API 文件則位於:
http://localhost:8080/redoc

這些文件都是 FastAPI 根據程式中的路由自動產生的。
如果執行:
kubectl get pods -n cka-lab
發現 Pod 狀態是:
ImagePullBackOff
或:
ErrImagePull
可以先查看詳細事件:
kubectl describe pod <Pod名稱> \
-n cka-lab
最常見原因是忘記將 Image 載入 kind:
kind load docker-image \
cka-api:v1 \
--name cka-lab
載入後,可以刪除失敗的 Pod,讓 Deployment 自動建立新的:
kubectl delete pod <Pod名稱> \
-n cka-lab
不需要手動建立 Pod。Deployment 與 ReplicaSet 會發現 Pod 數量不足,自動補上一個新的。
假設修改了:
return {
"message": "Hello v2"
}
只修改程式碼不會自動更新正在執行的 Pod。
完整更新流程應該是:
修改 main.py
↓
重新建立 Docker Image
↓
將新 Image 載入 kind
↓
讓 Kubernetes 建立使用新 Image 的 Pod
開發時最好不要一直重複使用相同 Tag,否則可能難以判斷 Node 使用的是新 Image 還是舊 Image。
例如可以建立:
docker build \
-t cka-api:v2 \
./app
載入 kind:
kind load docker-image \
cka-api:v2 \
--name cka-lab
接著把 YAML 裡的 Image 改成:
image: cka-api:v2
重新套用:
kubectl apply -f k8s/01-api.yaml
這時 Deployment 發現 Pod Template 已經改變,就會進行 Rolling Update。
也可以直接使用指令更新 Image:
kubectl set image deployment/api \
api=cka-api:v2 \
-n cka-lab
再觀察更新狀態:
kubectl rollout status deployment/api \
-n cka-lab
如果 Pod 是 Running,但是 Service 無法連線,可以按照以下順序檢查。
先確認 Pod Label:
kubectl get pods \
-n cka-lab \
--show-labels
確認其中包含:
app=api
再檢查 Service Selector:
kubectl describe service api \
-n cka-lab
確認 Selector 也是:
app=api
接著檢查 Endpoint:
kubectl get endpoints api \
-n cka-lab
如果沒有 Endpoint,通常代表 Service Selector 沒有匹配到任何 Pod。
如果有 Endpoint,還要確認 Application 是否真的監聽 Port 8000:
kubectl logs deployment/api \
-n cka-lab
應該能看到:
Uvicorn running on http://0.0.0.0:8000
前面幾天,我們部署的是別人已經準備好的 Nginx Image。今天則是第一次將自己撰寫的程式,完整送進 Kubernetes。
你完成的不是單純執行幾個指令,而是走過整條 Application Delivery Path:
Python Source Code
↓
requirements.txt 定義相依套件
↓
Dockerfile 定義執行環境
↓
docker build 建立 Image
↓
kind load 將 Image 放進 Node
↓
Deployment 定義期望狀態
↓
ReplicaSet 維持兩個 Replica
↓
Pod 啟動 FastAPI Container
↓
Service 找到 app=api 的 Pod
↓
port-forward 連接 Mac 與 Service
↓
瀏覽器或 curl 收到 JSON Response
更重要的是,現在你應該能區分每一層的責任:
FastAPI
負責 Application 邏輯
Uvicorn
負責監聽 HTTP Port
Docker Image
負責封裝程式與執行環境
Container
負責執行 Image
Pod
負責在 Kubernetes 中承載 Container
Deployment
負責管理 Replica 與更新
Service
負責提供穩定入口並尋找 Pod
port-forward
負責暫時讓本機連入 Cluster
如果未來服務發生問題,我們就可以按照這些層次逐一判斷:
程式能不能執行?
Docker Container 能不能啟動?
Pod 是否為 Running?
Service 是否找到 Endpoint?
Port 是否正確對應?
這就是為什麼我們前面先用 Nginx 學 Kubernetes,再從今天開始加入自己的 Application。
今天不只是把 FastAPI 放進 Kubernetes,而是第一次真正打通:
從原始碼到 Kubernetes 服務
的完整流程。